package com.xx.springsecuritydemo.main.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xx.springsecuritydemo.main.filter.LoginKaptchaFilter;
import com.xx.springsecuritydemo.main.handler.MyAccessDeniedHandler;
import com.xx.springsecuritydemo.main.handler.MyAuthenticationFailureHandler;
import com.xx.springsecuritydemo.main.handler.MyAuthenticationSuccessHandler;
import com.xx.springsecuritydemo.main.handler.MyLogoutSuccessHandler;
import com.xx.springsecuritydemo.main.security.CustomAuthenticationEntryPoint;
import com.xx.springsecuritydemo.main.security.CustomSecurityMetadataSource2;
import com.xx.springsecuritydemo.main.security.MyFilterSecurityInterceptor;
import com.xx.springsecuritydemo.main.security.MyPersistentTokenBasedRememberMeServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfigurer3 extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    private final MyAccessDeniedHandler accessDeniedHandler;

    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    @Autowired
    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    /**
     * 密码加密方式
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    public WebSecurityConfigurer3(UserDetailsService userDetailsService,
                                  MyAccessDeniedHandler accessDeniedHandler,
                                  CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
        this.userDetailsService = userDetailsService;
        this.accessDeniedHandler = accessDeniedHandler;
        this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
    }

    /**
     * 自定义AuthenticationManager
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("⾃定义AuthenticationManager: " + auth);
        // 使用自定义的userDetailsService
        auth.userDetailsService(userDetailsService);
    }


    //"作⽤: ⽤来将⾃定义AuthenticationManager在⼯⼚中进⾏暴露,可以在任何位置注⼊
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 记住我
     * @return
     */
    @Bean
    public RememberMeServices rememberMeServices() {
        return new MyPersistentTokenBasedRememberMeServices(
                UUID.randomUUID().toString(),
                userDetailsService,
                new InMemoryTokenRepositoryImpl()
        );
    }

    @Bean
    public LoginKaptchaFilter loginKaptchaFilter() throws Exception {
        LoginKaptchaFilter loginKaptchaFilter = new LoginKaptchaFilter();
        // 1.认证 url
        loginKaptchaFilter.setFilterProcessesUrl("/doLogin");
        // 2.认证 接收参数
        loginKaptchaFilter.setUsernameParameter("username");
        loginKaptchaFilter.setPasswordParameter("password");
        loginKaptchaFilter.setKaptchaParameter("kaptcha");
        // 3.指定认证管理
        loginKaptchaFilter.setAuthenticationManager(authenticationManagerBean());
        // 指定⾃定义rememberMeService
        loginKaptchaFilter.setRememberMeServices(rememberMeServices());
        // 4.指定成功时处理
        loginKaptchaFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
        // 5.认证失败处理
        loginKaptchaFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
        return loginKaptchaFilter;
    }


    /**
     * 自定义认证请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .mvcMatchers("/yz.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                .and()
                .rememberMe()
                .key("uniqueAndSecret") // 设置一个唯一密钥
                .tokenValiditySeconds(86400) // 设置记住我的有效期为 1 天
                .and()
                .csrf().disable()
                //自定义权限拒绝处理类
                .exceptionHandling()
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .and() //添加自定义权限过滤器
                .addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

        http.addFilterAt(loginKaptchaFilter(), UsernamePasswordAuthenticationFilter.class);
    }

}
